using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Threading;

namespace FIFP.Common
{
    /// <summary>
    ///   Acts as the base class for all ModelView type classes
    /// </summary>
    public abstract class ViewModelCore :
            INotifyPropertyChanged,
            IDisposable,
            IDataErrorInfo {
        
        private Dispatcher _dispatcher;

        protected ViewModelCore() {
            return;
        }

        /// <summary>
        ///   Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        ///   Returns the user-friendly name of this object.
        ///   Child classes can set this property to a new value,
        ///   or override it to determine the value on-demand.
        /// </summary>
        public string DisplayName { get; protected set; }

        /// <summary>
        ///   Gets or sets the dispatcher. If we don't have an Window/UserControl and it is not explicitly set, property will return null.
        /// </summary>
        /// <value>The dispatcher.</value>
        protected Dispatcher Dispatcher {
            get {
                return _dispatcher ?? (_dispatcher = Deployment.Current == null ? null : Deployment.Current.Dispatcher);
            }

            set {
                _dispatcher = value;
            }
        }

        /// <summary>
        ///   Returns whether an exception is thrown, or if a Debug.Fail() is used
        ///   when an invalid property name is passed to the VerifyPropertyName method.
        ///   The default value is false, but subclasses used by unit tests might 
        ///   override this property's getter to return true.
        /// </summary>
        protected bool ThrowOnInvalidPropertyName { get; set; }

        /// <summary>
        ///   Gets the error message for the property with the given name.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The error message for the property. The default is an empty string ("").
        /// </returns>
        public virtual string this[string propertyName] {
            get {
                return string.Empty;
            }
        }

        /// <summary>
        ///   Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>
        ///   An error message indicating what is wrong with this object. The default is an empty string ("").
        /// </returns>
        public virtual string Error {
            get {
                return string.Empty;
            }
        }

        /// <summary>
        /// Dispatches a lambda on the dispatcher thread.
        /// </summary>
        /// <param name="action">
        /// The block of code to execute on the dispatcher thread.
        /// </param>
        protected void Dispatch(Action action) {
            //ArgumentHelper.AssertNotNull(action, "action");
            if(Dispatcher == null || Dispatcher.CheckAccess())
                action();
            else
                Dispatcher.BeginInvoke(action);
        }

        /// <summary>
        /// Dispatches a lambda returning a value on the dispatcher thread.
        /// </summary>
        /// <typeparam name="TArg1">
        /// The type of the arg1.
        /// </typeparam>
        /// <param name="action">
        /// The block of code to execute on the dispatcher thread.
        /// </param>
        /// <param name="arg1">
        /// The arg1.
        /// </param>
        protected void Dispatch<TArg1>(Action<TArg1> action, TArg1 arg1) {
            //ArgumentHelper.AssertNotNull(action, "action");
            if(Dispatcher == null || Dispatcher.CheckAccess())
                action(arg1);
            else
                Dispatcher.BeginInvoke(action, arg1);
        }

        /// <summary>
        ///   See <see cref = "System.IDisposable.Dispose" /> for more information.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Called when the object is being disposed or finalized.
        /// </summary>
        /// <param name="disposing">
        /// True when the object is being disposed (and therefore can
        ///   access managed members); false when the object is being finalized without first
        ///   having been disposed (and therefore can only touch unmanaged members).
        /// </param>
        protected virtual void Dispose(bool disposing) {
        }

        /// <summary>
        /// Called when [property changed].
        /// </summary>
        /// <param name="e">
        /// </param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
            var handler = PropertyChanged;
           
            if(handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(handler, new object[] { this, e });
        }

        protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;

            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(handler, new object[] { sender, e });
        }

        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            this.OnPropertyChanged(propertyExpression.Member<T>().Name);
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if (propertyChanged != null)
            {
                PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
                propertyChanged.Invoke(this, args);
            }
        }


        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
        protected void RaisePropertyChanged(string propertyName) {
            VerifyPropertyName(propertyName);
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <typeparam name="TValue">
        /// The type of the value.
        /// </typeparam>
        /// <param name="expression">
        /// The expression.
        /// </param>
        /// <param name="newValue">
        /// The new value.
        /// </param>
        /// <param name="setter">
        /// The setter.
        /// </param>
        /// <returns>
        /// </returns>
        protected virtual bool SetPropertyValue<TValue>(Expression<Func<TValue>> expression, TValue newValue, Action<TValue> setter) {
            return SetPropertyValue(expression, newValue, setter, null);
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="setter">The setter.</param>
        /// <param name="validator">The validator.</param>
        /// <returns></returns>
        protected virtual bool SetPropertyValue<TValue>(Expression<Func<TValue>> expression, TValue newValue, Action<TValue> setter, Func<TValue, bool> validator) {
            var current = expression.Compile()();

            // only act if the new value is in fact different from the old
            var equal = EqualityComparer<TValue>.Default.Equals(current, newValue);

            if(equal)
                return false;

            var property = expression.Property();

            if(validator != null && !validator(newValue)) {
                RaisePropertyChanged("Error");
            }

            setter(newValue);

            RaisePropertyChanged(property.Name);

            return true;
        }

        /// <summary>
        /// Warns the developer if this object does not have
        ///   a public property with the specified name. This 
        ///   method does not exist in a Release build.
        /// </summary>
        /// <param name="propertyName">
        /// </param>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        protected void VerifyPropertyName(string propertyName) {

#if !SILVERLIGHT

            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if(TypeDescriptor.GetProperties(this)[propertyName] != null)
                return;

            var msg = "Invalid property name: " + propertyName;

            if(ThrowOnInvalidPropertyName)
                throw new Exception(msg);

            Debug.Fail(msg);

#endif
        }
    }
}